package panda.client;

import java.util.ArrayList;
import java.util.List;

import panda.client.engine.ClientModel;
import panda.client.engine.MagicCard;
import panda.client.engine.ServerModel;
import panda.client.enumerations.CardScale;
import panda.client.enumerations.ClientID;
import panda.client.enumerations.Color;
import panda.client.enumerations.MagicSet;
import panda.client.listeners.PandaKeyboardListener;
import panda.client.services.DataService;
import panda.client.services.DataServiceAsync;
import panda.client.ui.widgets.ActionBar;
import panda.client.ui.widgets.CombatZone;
import panda.client.ui.widgets.Counter;
import panda.client.ui.widgets.DraggableCard;
import panda.client.ui.widgets.NoteTip;
import panda.client.ui.widgets.TokenSummonDialog;
import panda.client.ui.widgets.Tooltip;
import panda.client.ui.widgets.ZoomManager;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

public class Cardpanda implements EntryPoint {
	public static VerticalPanel focusShell;
	public static FocusPanel focusPnl;
	public static AbsolutePanel canvas;
	public static TokenSummonDialog tokenMenu;
	public static ActionBar actionBar;
	public static ZoomManager zoomManager;

	// used only to control the random initial placement of cards; the actual canvaswidth is 100%
	public static final int canvasWidth = 900;
	public static final int canvasHeight = 550;
	public static final int widthBoundary = canvasWidth - CardScale.QUARTER.getWidth();
	public static final int heightBoundary = canvasHeight - CardScale.QUARTER.getHeight();
	public static final long doubleClickThreshold = 300;
	public static final boolean P2MIRROR = true; /* set to true to mirror P2's client: P2 = bottom */
	
	public static final boolean CLICK_TO_TAP = true; /* click quickly to tap, click and hold to drag */

	private boolean initialized = false;

	public static ClientModel clientModel;

	private static long startTime = (long) JSNI.getCurrentMs();

	public static long getElapsedMsSinceStart() {
		return (long) JSNI.getCurrentMs() - startTime;
	}

	public void onModuleLoad() {
		System.out.println("[c] start time: " + getElapsedMsSinceStart());

		focusShell = new VerticalPanel();
		focusPnl = new FocusPanel();
		canvas = new AbsolutePanel();
		tokenMenu = new TokenSummonDialog();

		focusPnl.addKeyboardListener(new PandaKeyboardListener());

		/* dummy panel to enclose focusPnl with a border, sigh :P */
		focusShell.add(focusPnl);
		DOM.setStyleAttribute(focusShell.getElement(), "border", "1px solid black");
		DOM.setStyleAttribute(focusShell.getElement(), "width", "100%");

		canvas.setHeight(canvasHeight + "px");
		DOM.setStyleAttribute(canvas.getElement(), "width", "100%");
		focusPnl.add(canvas);

		// RootPanel.get().add(focusShell);
	
		/* attack zone */
		CombatZone zone = new CombatZone();
		canvas.add(zone, 0, canvasHeight / 2 - zone.getStripeHeight() / 2);
		/* end attack zone code */

		registerClient();
	}

	private void registerClient() {
		AsyncCallback<Object> callback = new AsyncCallback<Object>() {
			public void onSuccess(Object result) {
				ClientID sessionID = (ClientID) result;
				System.out.println("[c] registered " + sessionID + ". Now retrieving model from server.");

				clientModel = new ClientModel(sessionID);
				zoomManager = new ZoomManager(clientModel.getObsLastMousedCard());

				loadNoteTipPanel();
				RootPanel.get().add(focusShell);
				
				DeferredCommand.addCommand(new Command() {
					public void execute() {
						focusPnl.setFocus(true);
					}
				});

				addActionBar();
				addTokenCreator();
				Tooltip.get().setVisible(false);
				canvas.add(Tooltip.get(), 0, 0);

				receiveModelFromServer();
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in registerClient");
				caught.printStackTrace();
			}
		};

		DataServiceAsync ds = (DataServiceAsync) GWT.create(DataService.class);
		ServiceDefTarget endpoint = (ServiceDefTarget) ds;
		String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
		endpoint.setServiceEntryPoint(moduleRelativeURL);
		ds.registerClient("this string will be used later to let the client input his own ID", callback);
	}

	private void receiveModelFromServer() {
		AsyncCallback<ServerModel> callback = new AsyncCallback<ServerModel>() {
			public void onSuccess(ServerModel model) {
				System.out.println("[c] " + clientModel.getSessionID() + ": received model " + model.getModelNum()
						+ " from server");
				clientModel.syncWithServerModel(model);

				/* the first time a model is received, broadcast to other clients that this client logged in. */
				/* this section must be called sync */
				if(!initialized) {
					initialized = true;
					
					/* 2008-07-22 1pm prefetch all images in both libraries */
					List<MagicCard> prefetchCards = new ArrayList<MagicCard>(); 
					prefetchCards.addAll(model.getP1lib());
					prefetchCards.addAll(model.getP2lib());
					
					for(MagicCard card : prefetchCards) {
						Image.prefetch(card.getImgUrl());
						Image.prefetch(card.getUrl50());
						Image.prefetch(card.getZoomUrl());
					}
					/* end prefetch */

					String name = Cardpanda.clientModel.getSessionID().getName();
					String logForMe = "You log in as " + name + ".";
					String logForOthers = name + " has logged in.";
					NoteTip.get().log(logForMe, logForOthers);
					sendModelUpdateToServer(clientModel.getSessionID());
				}

				/* Set up the next call in the infinite loop of getting updates and waiting until the server has updates */
				receiveModelFromServer();
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in getClientUpdates... going to retry");

				caught.printStackTrace();
				receiveModelFromServer();
			}
		};

		DataServiceAsync ds = (DataServiceAsync) GWT.create(DataService.class);
		ServiceDefTarget endpoint = (ServiceDefTarget) ds;
		String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
		endpoint.setServiceEntryPoint(moduleRelativeURL);
		ds.getModel(clientModel.getSessionID(), callback);
	}

	public static void sendModelUpdateToServer(final ClientID ID) {
		/* the callback! */
		AsyncCallback<Object> callback = new AsyncCallback<Object>() {
			public void onSuccess(Object result) {
				// result is null since updateModel is a void method
				System.out.println("[c] " + ID + "'s server update was successful");
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in updateServer");
				caught.printStackTrace();
			}
		};

		DataServiceAsync ds = (DataServiceAsync) GWT.create(DataService.class);
		ServiceDefTarget endpoint = (ServiceDefTarget) ds;
		String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
		endpoint.setServiceEntryPoint(moduleRelativeURL);
		ds.updateModel(ID, clientModel.createServerModelCopy(), callback);

		/**
		 * Increment model number AFTER the updateModel call is made. This is ok because if the model update fails on the
		 * server due to concurrent modification, the server returns its latest model (which will have the correct model
		 * number). If the model update succeeds, then the server will have incremented its model number as well.
		 */
		clientModel.incrementModelNumber();
	}
	
	/** Grabs a fresh serverID from the server for counter creation. */
	public static void createCounter(final Color color, final List<Counter> counters, final int x, final int y) {
		AsyncCallback<Object> callback = new AsyncCallback<Object>() {
			public void onSuccess(Object result) {
				Long serverID = (Long) result;
				System.out.println("[c] receiving serverID " + result + ".");
				
				Counter createdCounter = new Counter(color, serverID);
				
				counters.add(createdCounter);
				Cardpanda.canvas.add(createdCounter, x, y);
				

				String logForMe = "You add a counter.";
				String logForOthers = Cardpanda.clientModel.getSessionID().getName() + " adds a counter.";
				NoteTip.get().log(logForMe, logForOthers);
				
				Cardpanda.sendModelUpdateToServer(Cardpanda.clientModel.getSessionID());		
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure in createCounter");
				caught.printStackTrace();
			}
		};

		DataServiceAsync ds = (DataServiceAsync) GWT.create(DataService.class);
		ServiceDefTarget endpoint = (ServiceDefTarget) ds;
		String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
		endpoint.setServiceEntryPoint(moduleRelativeURL);
		ds.getNewServerID(callback);
	}

	/* adds the token creator dialog to the canvas (default hidden) */
	private void addTokenCreator() {
		canvas.add(tokenMenu, 200, 200);
	}

	/* initialize and add the actionbar */
	private void addActionBar() {
		actionBar = new ActionBar();
		RootPanel.get().add(actionBar);
	}

	/* initialize and add the noteTip panel: panel currently encloses noteTip with a bottom-margin */
	private void loadNoteTipPanel() {
		HorizontalPanel noteTipPnl = new HorizontalPanel();
		DOM.setStyleAttribute(noteTipPnl.getElement(), "marginBottom", "2px");
		RootPanel.get().add(noteTipPnl);

		noteTipPnl.add(NoteTip.get());
	}

	/**
	 * Queries the server for a MagicCard with MagicSet='set' and ID='id'. For code reability purposes, the name is also
	 * a parameter, and this method double checks that the name matches the name of the query result. Updates the server
	 * afterwards.
	 */
	public static void grabTokenByExactIDFromServer(String name, MagicSet set, int id) {
		AsyncCallback<MagicCard> callback = new AsyncCallback<MagicCard>() {
			public void onSuccess(MagicCard result) {
				MagicCard card = result;

				DraggableCard draggable = new DraggableCard(card);
				clientModel.addCardInPlay(draggable, 40, Cardpanda.canvasHeight / 2 - CardScale.QUARTER.getHeight() / 2);
				System.out.println(draggable.getName() + " added!");

				String logForMe = "You reveal " + draggable.getName() + ".";
				String logForOthers = clientModel.getSessionID().getName() + " reveals " + draggable.getName() + ".";
				NoteTip.get().log(logForMe, logForOthers);
				Cardpanda.sendModelUpdateToServer(Cardpanda.clientModel.getSessionID());
			}

			public void onFailure(Throwable caught) {
				System.out.println("failure");
			}
		};

		DataServiceAsync rs = (DataServiceAsync) GWT.create(DataService.class);
		ServiceDefTarget endpoint = (ServiceDefTarget) rs;
		String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
		endpoint.setServiceEntryPoint(moduleRelativeURL);
		rs.getCardViaExactID(name, set, id, callback);
	}

	// public void getCardsFromServer(String pattern) {
	// AsyncCallback<List<MagicCard>> callback = new AsyncCallback<List<MagicCard>>() {
	// public void onSuccess(List<MagicCard> result) {
	// for(MagicCard card : result) {
	// // cards.add(new DraggableCard(card));
	// p1hand.addCardToTop(new DraggableCard(card));
	// }
	//
	// for(DraggableCard card : draggableCardsInPlay) {
	// canvas.add(card, (int) Math.round(Math.random() * widthBoundary), (int) Math.round(Math.random()
	// * heightBoundary));
	// }
	//
	// loadBinExplorers();
	// }
	//
	// public void onFailure(Throwable caught) {
	// System.out.println("failure");
	// }
	// };
	//
	// DataServiceAsync rs = (DataServiceAsync) GWT.create(DataService.class);
	// ServiceDefTarget endpoint = (ServiceDefTarget) rs;
	// String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
	// endpoint.setServiceEntryPoint(moduleRelativeURL);
	// rs.getCardViaRegexText(pattern, callback);
	// }

	// public void populateBinViaName(String name, final Bin bin) {
	// AsyncCallback<MagicCard> callback = new AsyncCallback<MagicCard>() {
	// public void onSuccess(MagicCard result) {
	// bin.addCardToTop(new DraggableCard(result));
	// }
	//
	// public void onFailure(Throwable caught) {
	// System.out.println("failure");
	// }
	// };
	//
	// DataServiceAsync rs = (DataServiceAsync) GWT.create(DataService.class);
	// ServiceDefTarget endpoint = (ServiceDefTarget) rs;
	// String moduleRelativeURL = GWT.getModuleBaseURL() + "/DataService";
	// endpoint.setServiceEntryPoint(moduleRelativeURL);
	// rs.getCardViaExactName(name, callback);
	// }
}